// I developed this version under Windows 7, and the generated executable
// works fine on Windows XP.

#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

void quit(const char* device) {
	cerr << device  << ": No such file or directory" << endl;
	exit(-1);
}

void quit_timeout(const char* device) {
	cerr << device  << ": timed out" << endl;
	exit(-1);
}

void matchExpectedChars(HANDLE hSerial, int charsExpected, const char* device) {
  char buffer[2] = {0};
  char c;
  char lastChar = '\0';
  int charsReceived = 0;
  DWORD numBytes = 0;
  bool isLineStart = true;
  //cout << "charsExpected: " << charsExpected << endl;
  while (charsReceived < charsExpected) {
    ReadFile(hSerial, buffer, 1, &numBytes, NULL);
    if (numBytes == 0) {
      CloseHandle(hSerial);
      quit_timeout(device);
    }
    c = buffer[0];

	if (isLineStart && (c != '>' && c != ' ' && c != '\r' && c != '\n')) {isLineStart = false;}

    if (!isLineStart) {++charsReceived;}
	//cout << "'" << c << "' " << charsReceived << endl;
    cout << c << flush;

    if (!isLineStart && (c == '\r' || c == '\n')) {isLineStart = true;}
    if (isLineStart && lastChar == '>' && c == ' ') {isLineStart = false;}
    lastChar = c;
  }
}

void getAllResponses(HANDLE hSerial, int linesExpected, const char* device) {
	char buffer[2] = {0};
	char lastChar = '\0';
	int linesReceived = 0;
	DWORD numBytes = 0;
	while (linesReceived < linesExpected) {
	  ReadFile(hSerial, buffer, 1, &numBytes, NULL);
	  if (numBytes == 0) {
	    CloseHandle(hSerial);
	    quit_timeout(device);
	  }
	  if ((lastChar == '\n' || lastChar == '\0') && buffer[0] == '>') {++linesReceived;}
	  cout << buffer[0] << flush;
	  lastChar = buffer[0];
	}
}

void fix_com(char* port, char* result) {
	if (port[0] != 'C' || port[1] != 'O' || port[2] != 'M') {
		cout << port << " is not a valid serial port name" << endl;
		exit(-1);
	}
	
	char* num_part = port + 3;
	int port_num = atoi(num_part);

	if (port_num > 9) {
		strcpy(result+4,port);
		result[0] = '\\';
		result[1] = '\\';
		result[2] = '.';
		result[3] = '\\';
	} else {
		strcpy(result, port);
	}
}

int main(int argc, char* argv[]) {
	if (argc < 3) {
		cout << "Usage: fileToNxt COM[portNum] (-checkPort | filename) [-quit]" << endl;
		return 1;
	}

	char device[100];

	fix_com(argv[1], device);
	HANDLE hSerial = CreateFile(device, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hSerial == INVALID_HANDLE_VALUE) {
		quit(device);
	}

	if (!strcmp(argv[2], "-checkPort")) {
		CloseHandle(hSerial);
		return 0;
	}

	DCB dcbSerialParams = {0};
	dcbSerialParams.DCBlength = sizeof(dcbSerialParams);

	if (!GetCommState(hSerial, &dcbSerialParams)) {
		CloseHandle(hSerial);
		quit(device);
	}

	dcbSerialParams.BaudRate = CBR_9600;
	dcbSerialParams.ByteSize = 8;
	dcbSerialParams.StopBits = ONESTOPBIT;
	dcbSerialParams.Parity = NOPARITY;

	if (!SetCommState(hSerial, &dcbSerialParams)) {
		CloseHandle(hSerial);
		quit(device);
	}

	// Set up timeouts
	COMMTIMEOUTS timeouts={0}; 
	timeouts.ReadIntervalTimeout=1000; 
	timeouts.ReadTotalTimeoutConstant=1000; 
	timeouts.ReadTotalTimeoutMultiplier=1000; 

	timeouts.WriteTotalTimeoutConstant=1000; 
	timeouts.WriteTotalTimeoutMultiplier=1000; 
	if(!SetCommTimeouts(hSerial, &timeouts)){ 
	  CloseHandle(hSerial);
	  quit(device);
	} 

	ifstream inFile;
	inFile.open(argv[2], ios::in);

	int linesSent = 0, charsSent = 0;
	char buffer[2] = {0};
	char firstChar = '\0';
	DWORD numBytes = 0;
	while (!inFile.eof()) {
		inFile.get(buffer[0]);
		if (firstChar == '\0') {firstChar = buffer[0];}
		WriteFile(hSerial, buffer, 1, &numBytes, NULL);
		if (numBytes == 0) {
		  CloseHandle(hSerial);
		  quit_timeout(device);
		}
		++charsSent;
		//cout << "sent '" << buffer[0] << "' " << charsSent << endl;
		if (buffer[0] == '\n') {++linesSent;}
	}

	if (argc == 4) {
	  matchExpectedChars(hSerial, charsSent - 1, device); // Sends a double newline at the end, which is not echoed.
	} else {
	  getAllResponses(hSerial, linesSent, device);
	}

	CloseHandle(hSerial);
	return 0;
}
